package com.cskaoyan.javase.object._4hashcode;

/**
 * 方法的声明
 * hashCode()方法的方法声明：
 * public native int hashCode();
 * 很显然，它是一个本地方法，这个方法也没有任何参数，返回值是一个int类型整数。
 *
 * 方法的作用
 * 由于它是一个本地方法，我们通过查阅JDK文档，文档中关于它的作用说明了两点：
 *      1.返回该对象的哈希码值。
 *      2.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *
 * 关于哈希表、哈希码值等相关的概念，如果你对数据结构这门课程有些了解
 * 那么这些概念就是非常容易的。当然如果你还不太明白，就看一下下面的说明。
 *
 * 映射,允许出现多对一,但是绝不允许出现一对多
 *
 * 哈希也是一种特殊的映射，要求：
 *      1.集合A必须是一个无限大小，具有无穷多元素的集合。
 *      2.集合B必须是一个元素有限的集合。
 * 哈希映射是在"化无限为有限",这就是哈希
 *      1.在哈希映射当中，集合A和B之间的对应关系f，就是一种映射的规则，称之为哈希函数、哈希方法或者哈希算法等。
 *      2.而通过哈希算法，求得的集合B中的元素，求称之为哈希值。
 *      3.化无限为有限，这本身就是不现实的，更不可能强求映射是完全一一对应的。
 *          所以在哈希映射中，出现多对一是相当正常的，称之为"哈希冲突"。
 *          哈希冲突是一定,百分百存在的,不可能被避免
 *
 * 在Java当中哈希映射是以下:
 *      1.哈希方法,哈希算法,就是Object类当中的hashCode方法
 *      2.集合A是某个类的对象的集合,理论上来说,它是一个无限的集合
 *      3.集合B是int的整数集,是一个有限大小的集合
 *
 *      比如对于String类,我规定一个哈希算法: 字符串的长度是几,那么哈希值就是几
 *          以上算法规定是可以的,但是很明显非常不好,因为"哈希冲突"非常多
 *
 * hashCode方法的作用:
 *      1.返回该对象的哈希码值。就是hashCode方法的返回值整数,哈希码值决定了键值对在数组中的存储位置
 *      2.支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *          设计出更好的哈希算法,有利于减少哈希冲突
 *          这样在哈希表中"被挂起来"链表中的节点会更少,哈希表的性能会更强
 *
 * 以上问题弄明白以后,就只剩下最后一个问题:
 *      为什么equals方法要和hashCode方法一起重写?
 *      主要还是和哈希表的存储以及访问有关
 *      哈希表中,key是唯一性标识,value才是要存储的数据
 *      既然key是唯一的,那么如何在一个已存在节点的哈希表中确定key的唯一性呢?
 *      用key所属类的equals方法
 *      equals方法只要返回true,说明这个key已经存在了,于是要么不再存储,要么直接覆盖
 *              这时,在哈希映射当中,这两个key对象就是集合A中的同一个元素
 *              这样,在哈希映射中,它们两个key的哈希值必须相同!!!
 *              也就是两个key对象,如果equals方法返回true,那么它们hashCode的返回值必须相同
 *              (如果不相同就违背了映射的原则,也违背了哈希表的存储原则)
 *
 *      equals方法如果返回false,说明这个key不存在,那么就决定存储进去
 *              这情况下,会不会要求hashCode方法的返回值不一样呢?
 *              不会的,但是应该尽量保证不一样(减少哈希冲突,提升性能).
 *              但是一样也很正常,因为哈希冲突不可以避免
 *
 *     综上为什么要一起重写呢?
 *          如果equals方法返回true,那么它们hashCode的返回值必须相同(主要原因)
 *          要想保证以上原则,这两个方法必须一起重写,而且重写的条件必须是一样的
 *
 *
 *
 * 注意事项:
 *      1.一旦重写hashCode方法后
 *          默认的toString方法就不会再打印地址值了。
 *          这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值,重写后不再是地址值了。
 *          建议: 如果类中重写了hashCode方法,那么应该一起重写toString
 *
 *      2.如果类中有引用数据类型成员变量
 *          那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 *
 *
 * @since 15:47
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 700);
        Student s2 = new Student(18, 700);
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        Student s3 = new Student(20, 500);
        System.out.println(s1.equals(s3));
        System.out.println(s3.hashCode());

        System.out.println("------------------");
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}

class Student {
    int age;
    double score;
    String name;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (Double.compare(student.score, score) != 0) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }*/

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}